#include <linux/init.h>         //包含宏定义得头文件
#include <linux/module.h>       //包含初始化加载模块得头文件
#include <linux/io.h>           //ioermap等函数的头文件
#include <linux/platform_device.h>       //平台总线模型文件
#include <linux/of.h>           //设备树操作函数操作集
#include <linux/of_address.h>   //of_iomap
#include <linux/gpio.h>         //gpio子系统的使用需要的头文件
#include <linux/of_gpio.h>      //操作gpio子系统相关的函数使用的头文件 
#include <linux/interrupt.h>    //向内核注册中断号的函数头文件
#include <linux/of_irq.h>       //向内核注册中断号的函数头文件
#include <linux/timer.h>        //定时器头文件、
#include <linux/input.h>        //输入子系统
// #include <linux/cdevdevice.h>   //杂项设备头文件
/*包含头文件*/
/***********输入子系统相关的介绍*************
*（1）输入子系统是linux专门做的一套框架来处理事件如：鼠标，键盘，触摸屏等输入设备
*（2）可以在节点dev/input查看相关的设备节点，bexdump查看二进制文本工作 bexdump dev/input/event0
*（3）
*（4） 
*（5）
*（6）
* (7)注意点：查看设备树是否有添加上节点使用cd /proc/device-tree后使用ls查看，cat compatible可以查看属性
* (8)调试方法:cat /proc/irq/4/spurious(可以查看打印)
***********************************************************************/
/*宏定义*/
 

/*全局变量*/
struct device_node*test_device_node;//获取设备树节点的变量
struct property*property_test;
struct input_dev*input_test;        //定义一个输入子系统
//int beep_gpio=0;                //用于获取GPIO编号
int gpio_num;                   //中断实验获取gpio编号
int irq_num;                    //获取中断号
/*定义函数*/
static void timer_function(unsigned long data);

/*定时器变量动态定义*/
DEFINE_TIMER(test_timer,timer_function,0,0)//动态创建一个定时器结构体以及超时函数处理
/*文件操作集的结构体*/

/*超时处理函数*/
static void timer_function(unsigned long data)
{
    int value;
    value=!gpio_get_value(gpio_num);//获取管脚中断号
    /*结合中断延时函数上报数据，上报事件有针对的函数*/
    input_report_key(input_test,KEY_1,value);//上报函数
    input_sync(&input_test);//上报完后同步
    // printk("timer_function test!");
    // mod_timer(&test_time,jiffies+1*HZ);
}
/*中断处理函数*/
irq_handler_t test_key_IRQ(int irq,void *args)
{
    printk("helle irq\n");
    // mdelay(20);//不符合中断上文耗时的要求 启动
    test_time.rexpires = jiffies+msecs_to_jiffies(20);//延时20msjiffies+1*HZ;jiffies是当前时间//设置定时器的时间HZ可以在asm-generic/param文件中修改
    add_timer(&test_time);//向内核注册
    return IRQ_HANDLED;
}

/*************platform_driver内容处理************/
int beep_probe(struct platform_device*pdev)
{
    int ret;
    int irq_reg;
    int misc_reg;
    int input_reg;
    printk("beep_probe\n");
/*获取设备树信息的方法手段*/
    //方法一：直接获取设备树的节点信息
    printk("node name is %s\n",pdev->dev.of_node->name);
    /*方法二：先获取到设备树的设备节点后续再处理*/
    /*配置中断步骤一：获取到设备树的节点*/  
    test_device_node=of_find_node_by_path("/test_key");//获取节点的信息，/test取绝对路径
        if(test_device_node==NULL)
        {
            printk("test_device_node error!\n");
            return -1;
        }
    printk("test_device_node name is %s",test_device_node->name);

/*使用pinctrl-gpio子系统来处理gpio以及中断事物*/
    /*配置中断步骤二：获得GPIO的编号*/
    gpio_num=of_get_named_gpio(test_device_node,"gpios",0);//从设备树获取到 GPIO 编号
        if(gpio_num<0)
        {
             printk("of_get_named_gpio error!\n");
             return -1；
        }
        printk("gpio_num is :%n\n",gpio_num);
    irq_reg=gpio_request(gpio_num,"beep");//向内核申请指定gpio
        if(irq_reg<0)
            {
                printk("gpio_request error!\n");
                return -1；
            }
    /*配置中断步骤三：设置GPIO方向*/
    gpio_direction_input(gpio_num,1);//这个是设置该GPIO口为输出功能输出高电平是 1
    /*配置中断步骤四：获取中断号，有两种方法*/
    //方法一
    //irq_num=gpio_to_irq(gpio_num);       //从设备树获取到 中断号
    //方法二
    irq_num=irq_of_parse_and_map(test_device_node,0);//从设备树获取中断号 设备树中设置了interrupt属性
    printk("irq is :%d\n",irq_num);     
    /*配置中断步骤五：申请中断*/
    ret=request_irq(irq_num,test_key_IRQ,IRQF_TRLGGER_RISING|IRQF_TRLGGER_FALLING,"test_key",NULL); //IRQF_TRLGGER_RISING上升沿触发中断 向内核注册中断号含中断处理函数 参数test_key_IRQ中断处理函数
        if(ret<0)
            {
                printk("ret error!\n");
                return -1；
            }

/*初始化输入子系统*/
    input_test=input_allocate_device();
    //初始化结构体
    input_test->name="test_key";
    _set_bit(EV_KEY,input_test->evbit);//结构体需要支持按键
    _set_bit(KEY_1,input_test->keybit);//告诉内核需要支持按键
    input_reg=input_register_device(input_test);
    if(input_reg<0)
    {
        printk("input_register_device error!\n");
        goto  error_input_register;
    }

/*返回值*/
    return 0;
/*************各个部分注册失败需要注销操作****************/
    /*注销input*/
error_input_register:
    input_unregister_device(input_test);

}

int beep_remove(struct platform_device*pdev)
{
    printk("beep_remove byb!\n");
    return 0;
}

const struct platform_device_id beep_idtable={  //优先级更高的命名
    .name="beep_test"
};

/*.driver中的变量，匹配设备树的节点compatible属性*/
const struct of_device_id of_match_table_test[]={  
    {.compatible="test1234"},
    {}

};

struct platform_driver beep_driver={   //通过.name名字与device匹配成功后会执行prode函数
    .probe =beep_probe,
    .remove=beep_remove,
    .driver={                           //该结构体的变量是能够通过自身的变量获取到设备树文件的信息
        .owner = THIS_MODULE,
        .name="beep_test",
        .of_match_table=of_match_table_test
    },
    .id_table = &beep_idtable           //该变量比.driver中的.name优先级要高
};

/*功能实现*/
static int beep_driver_init(void)
{
    int plat_reg=0;
    plat_reg =platform_driver_register(&beep_driver);
    if(plat_reg<0){
        printk("platform_driver_register error!\n");
    }
    printk("platform_driver_register ok!\n");

    return 0;
}

static void beep_driver_exit(void)//退出函数没有返回值
{
    platform_driver_unregister(&beep_driver);
    gpio_free(gpio_num);//释放GPIO编号
    free_irq(irq_num,NULL);
    del_timer(&test_time);
    input_unregister_device(input_test);//卸载输入子系统
    printk("byb byb !");  
}     

module_init(beep_driver_init);
module_exit(beep_driver_exit); 
/*驱动的出口与入口函数都在此处*/
MODULE_LICENSE("GPL");
/*申明信息*/
	
/*注意在开发板中加载驱动的时候一定要保持板卡的内核和编译的内核是一致的否则会报错
*查看设备节点：ls /dev/hello_cdev
*/


